#include <Windows.h>
#include <vector>
#include "stream.h"
#include "process_mgmt.h"
#include "time_mgmt.h"

using namespace std;


static DWORD WINAPI timer_loop(LPVOID lpParam);
static VOID timoutUpdateFunction();

struct timer_loop_param {
    timer_loop_param():ms_interval(0), userDefFct(NULL), killEventRaw(NULL) {}
    timer_loop_param(UINT ms_ival, VOID (*fct)(VOID), UINT_PTR killEvtRaw): ms_interval(ms_ival), userDefFct(fct), killEventRaw(killEvtRaw) {}

    UINT     ms_interval;
    VOID     (*userDefFct)(VOID);
    UINT_PTR killEventRaw;
};


void aa();
void bb();
void test()
{
    INT delayCounter  = 20000;
    static INT delayCounter1 = 0;

    UINT timerID_2     = timers::timer_create(1000, bb);
    UINT timerID_5     = timers::timer_create(1000, bb);
    UINT timerID_1     = timers::timer_create(800, aa);

    UINT timerID_6     = timers::timer_create(1000, bb);
    UINT timerID_7     = timers::timer_create(1000, bb);
    UINT timerID_8     = timers::timer_create(1000, bb);
    UINT timerID_9     = timers::timer_create(1000, bb);
    UINT timerID_10    = timers::timer_create(1000, bb);
    UINT timerID_21    = timers::timer_create(1000, bb);
    UINT timerID_51    = timers::timer_create(1000, bb);
    //UINT timerID_61    = timers::timer_create(1000, bb);
    //UINT timerID_71    = timers::timer_create(1000, bb);
    //UINT timerID_81    = timers::timer_create(1000, bb);
    //UINT timerID_91    = timers::timer_create(1000, bb);
    //UINT timerID_101   = timers::timer_create(1000, bb);
    UINT timeroutID_4 = timers::timeout_peek_create(delayCounter);
    UINT timeroutID_3 = timers::timeout_peek_create(delayCounter);

    int i = 0;
    while(i < 22) {

        INT timeroutID_5 = timers::timeout_peek_create(1000);
        //timers::timeout_create(1000);
        delayCounter  = delayCounter1;
        delayCounter1 = timers::timeout_peek_left_ms(timeroutID_3);
        delayCounter -= delayCounter1;
        i++;
        OUTSTREAM<<i<<". Timeout d: "<<delayCounter<<" | "<<delayCounter1<<endl<<endl;
        PRINTOUT;
        if(i == 6 ) timers::timer_kill(timerID_2);
        if(i == 8 ) timers::timer_kill(timerID_1);
        if(i == 16) timers::timeout_kill(timeroutID_3);

        while(1)
            if(timers::timeout_peek_left_ms(timeroutID_5) <= 0)
                break;

    }
}

void aa()
{
    OUTSTREAM<<"TEST aaas"<<endl;
    //Sleep(2000);

    PRINTOUT;
    //while(1);
}

void bb()
{
    OUTSTREAM<<"TEST bb"<<endl;
    PRINTOUT;
}

/******************************************************************************
*
*               delay class members
*
******************************************************************************/
class delay {
public:
    delay(): currentTimePoint(0), timeDiff(0) {}
    delay(LONGLONG referenceTimePoint) { this->delay::delay(); calculateTimeDiff(referenceTimePoint); }

    FunctionSuccesFailure create_msDelay(UINT msDelay);
    LONGLONG getCurrentTimePoint() { return currentTimePoint; }
    DOUBLE getTimeDiff() { return timeDiff; }
    DOUBLE getTimeDiff(LONGLONG referenceTimePoint) { calculateTimeDiff(referenceTimePoint); return getTimeDiff(); }

private:
    static const DOUBLE FreqMsTrans;
    static const UINT_PTR delayMutex;
    static DOUBLE Freq;
    LONGLONG currentTimePoint;
    DOUBLE timeDiff;

    static DOUBLE getFreq();
    VOID calculateTimeDiff(LONGLONG referenceTimePoint);
};

DOUBLE   const delay::FreqMsTrans = 1000.0;
UINT_PTR const delay::delayMutex  = processes::mutex_create(UNNAMED);
DOUBLE   delay::Freq              = delay::getFreq();

inline DOUBLE delay::getFreq() {
    LARGE_INTEGER freq_base = {0};

    QueryPerformanceFrequency(&freq_base);
    return (DOUBLE(freq_base.QuadPart)/FreqMsTrans);
}

inline VOID delay::calculateTimeDiff(LONGLONG referenceTimePoint)
{
    static const DOUBLE roundLimit = 0.95;
    static const DOUBLE roundValue = 1.000001;
    LARGE_INTEGER currentTime = {0};
    DOUBLE timeDiff = 0;

    processes::mutex_take_infinite(delayMutex);
    QueryPerformanceCounter(&currentTime);
    processes::mutex_release(delayMutex);

    timeDiff = (currentTime.QuadPart - referenceTimePoint)/Freq;

    if( (timeDiff - (UINT)(timeDiff)) > roundLimit ) {
        timeDiff = (UINT)(timeDiff) + roundValue;
    }

    this->timeDiff = timeDiff;
    this->currentTimePoint = currentTime.QuadPart;
}

inline FunctionSuccesFailure delay::create_msDelay(UINT msDelay)
{
    LONGLONG startTimePoint = 0;

    calculateTimeDiff(startTimePoint);
    startTimePoint = getCurrentTimePoint();
    while((DOUBLE)msDelay > getTimeDiff(startTimePoint));

    return FUNCTION_SUCCESS;
}

/******************************************************************************
*
*               timers singleton class members
*
******************************************************************************/
UINT_PTR const timers::timerMutex = processes::mutex_create(UNNAMED);
vector<timers::tim_info> timers::timersData(MaxTimersLimit, tim_info());

VOID timoutUpdateFunction()
{
    timers::updatePeekTimeouts();
}

DWORD WINAPI timer_loop(LPVOID lpParam)
{
    timer_loop_param timerParam = *(timer_loop_param*)lpParam;

    if( timerParam.ms_interval > 0 && timerParam.userDefFct != NULL ) {
        processes::instance().thread_ready();

        delay new_delay;
        while(processes::event_wait(timerParam.killEventRaw, 0) == FUNCTION_FAILURE){
            timerParam.userDefFct();
            new_delay.create_msDelay(timerParam.ms_interval);
        }

        processes::event_destroy(timerParam.killEventRaw);
        stream::system_message("time_mgmt" ,"timer killed!");

        return EXIT_SUCCESS;
    }
    
    return EXIT_FAILURE;
}

FunctionSuccesFailure timers::getFreeTimer(UINT &freeTimer)
{
    for(UINT tim=0; tim<MaxTimersLimit; ++tim) {
        if(timersData[tim].taken == FALSE) {
            freeTimer = tim;
            return FUNCTION_SUCCESS;
        }
    }

    stream::error_message("time_mgmt", "All free timers depleted!");

    return FUNCTION_FAILURE; 
}

FunctionSuccesFailure
timers::setTaken(UINT timer, timerKind Kind, UINT_PTR hKillEvtRaw, UINT tout, UINT_PTR hThdRaw, VOID (*fct)(VOID), UINT ival)
{
    if( timer < MaxTimersLimit && timersData[timer].taken == FALSE ) {
        timersData[timer] = tim_info(Kind, hKillEvtRaw, tout, hThdRaw, fct, ival);
        return FUNCTION_SUCCESS;
    }

    return FUNCTION_FAILURE;
}

FunctionSuccesFailure timers::releaseTaken(UINT timer)
{
    if( timer < MaxTimersLimit && timersData[timer].taken == TRUE ) {
        timersData[timer] = tim_info();
        return FUNCTION_SUCCESS;
    }

    return FUNCTION_FAILURE;
}

INT timers::timer_create(const UINT ms_interval, VOID (*ext_fct)(VOID) )
{
    processes::mutex_take_infinite(timerMutex);

    UINT fresult  = FUNCTION_FAILURE;
    UINT newTimer = 0;

    if(getFreeTimer(newTimer) != FUNCTION_FAILURE) {
        UINT_PTR hKillEventRaw = processes::event_create(FALSE, UNNAMED);
        if(hKillEventRaw != NULL) {
            timer_loop_param param(ms_interval, ext_fct, hKillEventRaw);
            UINT_PTR hThreadRaw = processes::thread_create(timer_loop, (LPVOID*)&param, 0);
            if(hThreadRaw != NULL) {
                setTaken(newTimer, IDT_KTIMER, hKillEventRaw, 0, hThreadRaw, ext_fct, ms_interval);
                fresult = (INT)newTimer;
            }
        }
    }

    processes::mutex_release(timerMutex);

    return fresult;
}

INT timers::timeout_peek_create(const UINT timeout)
{
    static const UINT tMsUpdateRate    = 1;
    static VOID (*const ext_fct)(VOID) = timoutUpdateFunction;
    static UINT_PTR hKillEventRaw      = NULL;

    processes::mutex_take_infinite(timerMutex);

    UINT fresult    = FUNCTION_FAILURE;
    UINT newTimeout = 0;

    if(getFreeTimer(newTimeout) != FUNCTION_FAILURE) {
        if(isTimeoutDeployed() == FALSE) {
            UINT_PTR hKillEventRaw = processes::event_create(FALSE, UNNAMED); 
            if(hKillEventRaw != NULL) {
                timer_loop_param param(tMsUpdateRate, ext_fct, hKillEventRaw);
                UINT_PTR hThreadRaw = processes::thread_create(timer_loop, (LPVOID*)&param, 0);
                if(hThreadRaw != NULL) {
                    setTaken(newTimeout, IDT_KTIMEOUT, hKillEventRaw, timeout, hThreadRaw, ext_fct, tMsUpdateRate); 
                    fresult = newTimeout;
                }
            }
        }
        else {
            setTaken(newTimeout, IDT_KTIMEOUT, hKillEventRaw, timeout, NULL, ext_fct, tMsUpdateRate); 
            fresult = newTimeout; 
        }
    }

    processes::mutex_release(timerMutex);

    return fresult;
}

FunctionSuccesFailure timers::timeout_create(const UINT ms_interval)
{
    delay new_delay;
    return new_delay.create_msDelay(ms_interval);
};

FunctionSuccesFailure timers::timer_kill(UINT timer)
{
    if( timer < MaxTimersLimit && timersData[timer].taken == TRUE ) {
        if( timersData[timer].Type == IDT_KTIMER || (timersData[timer].Type == IDT_KTIMEOUT && isTimeoutLast() == TRUE) )
            destroyTimerThread(timer);
        releaseTaken(timer);

        return FUNCTION_SUCCESS;
    }

    return FUNCTION_FAILURE;
}

FunctionSuccesFailure timers::timeout_kill(UINT timeoutRaw)
{
    return timer_kill(timeoutRaw);
}

VOID timers::updatePeekTimeouts()
{
    static LONGLONG startTimePoint = 0;
    UINT decrTimeMsInterv = 0;
    UINT timeouts_amount  = 0;
    UINT timeout_number   = 0;
    UINT timeDiff         = 0;

    delay newTimePoint(startTimePoint);
    if(startTimePoint != 0) {
        timeDiff = (UINT)newTimePoint.getTimeDiff();
    }
    startTimePoint = newTimePoint.getCurrentTimePoint();

    for(UINT tim=0; tim<MaxTimersLimit; ++tim) {
        if( timersData[tim].taken == TRUE && timersData[tim].Type == IDT_KTIMEOUT ) {
            ++timeouts_amount;
            timeout_number = tim;
            if(timersData[tim].TimeoutDelayMs > timeDiff)
                timersData[tim].TimeoutDelayMs -= timeDiff;
            else
                timersData[tim].TimeoutDelayMs = 0;
        }
    }

    if( timeouts_amount == 1 && timersData[timeout_number].TimeoutDelayMs == 0 )
        destroyTimerThread(timeout_number);
}

INT timers::timeout_peek_left_ms(UINT timeout)
{
    if( timeout < MaxTimersLimit && timersData[timeout].taken == TRUE ) {
        UINT TimeoutDelayMs = timersData[timeout].TimeoutDelayMs;
        if(TimeoutDelayMs == 0) 
            releaseTaken(timeout);
        return TimeoutDelayMs;
    }

    return FUNCTION_FAILURE;
};

BOOL timers::isTimeoutDeployed()
{
    for(UINT tim=0; tim<MaxTimersLimit; ++tim) 
        if( timersData[tim].taken == TRUE && timersData[tim].Type == IDT_KTIMEOUT )
            return TRUE;

    return FALSE;
}

BOOL timers::isTimeoutLast()
{
    UINT timeouts = 0;
    for(UINT tim=0; tim<MaxTimersLimit; ++tim) {
        if( timersData[tim].taken == TRUE && timersData[tim].Type == IDT_KTIMEOUT ) {
            ++timeouts;
            if(timeouts > 1)
                return FALSE;
        }
    }

    return TRUE;
}

FunctionSuccesFailure timers::destroyTimerThread(UINT timer)
{
    if( timer < MaxTimersLimit && timersData[timer].taken == TRUE )
        return processes::event_signal(timersData[timer].hKillEventRaw);

    return FUNCTION_FAILURE; 
}

